package com.example.demo.configure;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;

@Aspect
@Component
@Slf4j
public class WebRequestLogAspect {

    private final Logger loggerController = LoggerFactory.getLogger("ControllerRequest");
    private final Logger logger = LoggerFactory.getLogger(WebRequestLogAspect.class);
    ThreadLocal<Long> startTime = new ThreadLocal<>();
    ThreadLocal<String> beanName = new ThreadLocal<>();
    ThreadLocal<String> user = new ThreadLocal<>();
    ThreadLocal<String> methodName = new ThreadLocal<>();
    ThreadLocal<String> params = new ThreadLocal<>();
    ThreadLocal<String> remoteAddr = new ThreadLocal<>();
    ThreadLocal<String> uri = new ThreadLocal<>();

    private static Map<String, Object> getFieldsName(ProceedingJoinPoint joinPoint) {
        // 参数值
        Object[] args = joinPoint.getArgs();
        ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String[] parameterNames = pnd.getParameterNames(method);
        Map<String, Object> paramMap = new HashMap<>(32);
        for (int i = 0; i < parameterNames.length; i++) {
            paramMap.put(parameterNames[i], args[i] + "(" + args[i].getClass().getSimpleName() + ")");
        }
        return paramMap;
    }

    @Pointcut("execution(public * com.example.demo.controller..*.*(..))")
    public void webRequestLog() {
    }

//    @Pointcut("execution(* com.example.demo..repository..*(..))")
//    private void repositoryOps() {
//    }
//
//    @Around("repositoryOps()")
//    public Object logPerformance(ProceedingJoinPoint pjp) throws Throwable {
//        long startTime = System.currentTimeMillis();
//        String name = "-";
//        String result = "Y";
//        try {
//            name = pjp.getSignature().toShortString();
//            return pjp.proceed();
//        } catch (Throwable t) {
//            result = "N";
//            throw t;
//        } finally {
//            long endTime = System.currentTimeMillis();
//            log.info("{};{};{}ms", name, result, endTime - startTime);
//        }
//    }


    /**
     * 前置通知，方法调用前被调用
     *
     * @param joinPoint
     */
    @Before("webRequestLog()")
    public void doBefore(JoinPoint joinPoint) {
        try {
            startTime.set(System.currentTimeMillis());
            // 接收到请求，记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            beanName.set(joinPoint.getSignature().getDeclaringTypeName());
            methodName.set(joinPoint.getSignature().getName());
            uri.set(request.getRequestURI());
            remoteAddr.set(getIpAddr(request));
            user.set((String) request.getSession().getAttribute("user"));
        } catch (Exception e) {
            logger.error("***操作请求日志记录失败doBefore()***", e);
        }
    }

    /**
     * 环绕通知,环绕增强，相当于MethodInterceptor
     *
     * @param thisJoinPoint
     */
    @Around("webRequestLog()")
    public Object proceed(ProceedingJoinPoint thisJoinPoint) throws Throwable {
        Object object = thisJoinPoint.proceed();
        Map<String, Object> fieldsName = getFieldsName(thisJoinPoint);
        params.set(fieldsName.toString());
        return object;
    }

    /**
     * 处理完请求返回内容
     *
     * @param result
     */
    @AfterReturning(returning = "result", pointcut = "webRequestLog()")
    public void doAfterReturning(Object result) {
        try {
            long requestTime = (System.currentTimeMillis() - startTime.get()) / 1000;
            loggerController.info("请求耗时：" + requestTime + ", uri=" + uri.get() + "; beanName=" + beanName.get() + "; remoteAddr=" + remoteAddr.get() + "; user=" + user.get()
                    + "; methodName=" + methodName.get() + "; params=" + params.get() + "; RESPONSE : " + result);

        } catch (Exception e) {
            logger.error("***操作请求日志记录失败doAfterReturning()***", e);
        }
    }

    /**
     * 获取登录用户远程主机ip地址
     *
     * @param request
     * @return
     */
    private String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
            if (ip.equals("127.0.0.1") || ip.equals("0:0:0:0:0:0:0:1")) {
                //根据网卡取本机配置的IP
                InetAddress inet = null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                ip = inet.getHostAddress();
            }
        }
        // 多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割
        if (ip != null && ip.length() > 15) {
            if (ip.indexOf(",") > 0) {
                ip = ip.substring(0, ip.indexOf(","));
            }
        }
        return ip;
    }

}
